package idbx

import (
	"errors"
	"gitee.com/tietang/terrace-go/v2/base/starter/idgen"
	log "github.com/sirupsen/logrus"
	"github.com/tietang/dbx"
	"strconv"
	"sync"
	"time"
)

//32位
// CREATE TABLE `po0`.`worker_id`  (
//  `worker_name` varchar(64) NOT NULL DEFAULT 'default',
//  `worker_id_incr` bigint(20) UNSIGNED NOT NULL DEFAULT 1,
//  PRIMARY KEY (`worker_name`)
//);
// code:7,time:14+3,defaultWorkerId:4,incrid:4
//0000012.20190104145814.883.0001.0001
// LocalIdGeneratorRdbFactory struct
type LocalIdGeneratorRdbFactory struct {
	db           *dbx.Database
	WorkerIdMax  int64
	idGenerators map[string]idgen.IdGenerator
	lock         *sync.Mutex
	GenFunc      func(code int, millisecond int64, timestamp string, workId, incrId int64) interface{}
}

// NewIDGenerator construct
func NewLocalIdGeneratorRdbFactory(db *dbx.Database) *LocalIdGeneratorRdbFactory {

	gen := &LocalIdGeneratorRdbFactory{
		db:           db,
		WorkerIdMax:  idgen.WorkerMax,
		lock:         &sync.Mutex{},
		idGenerators: make(map[string]idgen.IdGenerator),
	}
	gen.GenFunc = idgen.FuncCodeString

	//定时刷新workId 缓存来续约
	ticker := time.NewTicker(50 * time.Minute)
	go func() {
		for {
			<-ticker.C
			for k, v := range gen.idGenerators {
				update := "update worker_renewal set expired_at=date_add(CURRENT_TIMESTAMP(6), interval 1 hour) where worker_name=? and worker_id=?"
				id, rows, err := gen.db.Execute(update, v.GetWorkerName(), v.GetWorkerId())
				if err == nil {
					log.Infof("Renewal workerId：%s %v+, %d %d", k, v, id, rows)
				} else {
					log.Error(err)
				}
			}
		}
	}()

	return gen
}

func (idGen *LocalIdGeneratorRdbFactory) GetWorkId(workerName string) int64 {
	gen, ok := idGen.idGenerators[workerName]
	if !ok {
		gen, _ = idGen.Create(workerName)
	}
	return gen.GetWorkerId()
}

func (idGen *LocalIdGeneratorRdbFactory) GetOrCreate(workerName string) (idgen.IdGenerator, error) {
	gen, ok := idGen.idGenerators[workerName]
	var err error
	if !ok {
		gen, err = idGen.Create(workerName)
	}
	return gen, err
}

func (idGen *LocalIdGeneratorRdbFactory) CreateWorkId(gen *idgen.LocalIdGenerator) (workerIdKey string, workerId int64, err error) {

	//sql := "select worker_name,worker_id,expired_at,updated_at from worker_renewal where worker_name=? and worker_id=?"
	isGetWorkerId := false
	for i := 0; i <= int(idGen.WorkerIdMax); i++ {

		// 再获取一个自增ID
		//INSERT INTO aaa(alarm_id,text) VALUES (-1,'最新执行完成时间') ON DUPLICATE KEY UPDATE text=VALUES(text);
		update := "INSERT INTO worker_id(worker_name,worker_id_incr) VALUES (?,LAST_INSERT_ID(1))" +
			" ON DUPLICATE KEY " +
			"update worker_id_incr=LAST_INSERT_ID(worker_id_incr+1)"

		rs, err := idGen.db.Exec(update, gen.GetWorkerName())
		id, err := rs.LastInsertId()
		//然后检查续约
		workerId = id % idGen.WorkerIdMax
		wr := &idgen.WorkerRenewal{WorkerId: workerId, WorkerName: gen.GetWorkerName()}

		ok, err := idGen.db.GetOne(wr)
		if err != nil {
			log.Error(err)
			continue
		}
		if ok {
			//fmt.Println(wr)
			//if wr.ExpiredAt.Unix() > time.Now().Unix()+3600 || wr.UpdatedAt.Unix()+3600 > time.Now().Unix() {
			//	return errors.New("the worker id has not expired: ")
			//}
			if wr.UpdatedAt.Unix()+3600 > time.Now().Unix() {
				continue
			}
			update := "update worker_renewal set expired_at=date_add(CURRENT_TIMESTAMP(6), interval 1 hour) where worker_name=? and worker_id=?"
			id, rows, err := idGen.db.Execute(update, gen.GetWorkerName(), workerId)
			if err == nil {
				log.Infof("Renewal workerId： %d %d", id, rows)
			} else {
				log.Error(err)
				continue
			}
		} else {
			//创建续约
			insert := "insert into worker_renewal(worker_name,worker_id,expired_at) values(?,?,date_add(CURRENT_TIMESTAMP(6), interval 1 hour)) "
			id, rows, err := idGen.db.Execute(insert, gen.GetWorkerName(), workerId)
			if err == nil {
				log.Infof("Renewal workerId： %d %d", id, rows)
			} else {
				log.Error(err)
				continue
			}
		}
		gen.WorkerId = workerId
		isGetWorkerId = true
		break
	}
	if !isGetWorkerId {
		log.Panic(errors.New("not get worker id"))
	}

	return "_workerId:" + gen.GetWorkerName() + ":" + strconv.Itoa(int(workerId)), gen.GetWorkerId(), err
}

func (idGen *LocalIdGeneratorRdbFactory) ListIdGenerators() map[string]idgen.IdGenerator {
	return idGen.idGenerators
}

func (idGen *LocalIdGeneratorRdbFactory) Create(workerName string) (generator idgen.IdGenerator, err error) {
	gen := idgen.NewLocalIdGenerator(0, workerName)
	gen.GenFunc = idGen.GenFunc
	idKey, workerId, err := idGen.CreateWorkId(gen)
	if err != nil {
		return nil, err
	}
	gen.WorkerIdKey = idKey
	gen.WorkerId = workerId
	idGen.idGenerators[workerName] = gen
	log.Infof("generate workerId: %s = %d", idKey, workerId)
	return gen, err
}
